package com.gitee.loyo.interfaces

import java.lang.RuntimeException
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KParameter
import kotlin.reflect.full.*

interface Reflection {

    object DEFAULT : Reflection {

        private val fieldCanAccess: Boolean = Field::class.java.declaredFields.firstOrNull {
            it.name == "canAccess"
        } != null

        override fun <T: Any> newInstance(type: KClass<T>, vararg args: Any?): T {
            val construct = type.primaryConstructor!!
            val parameters = construct.parameters
            return when{
                args.isNotEmpty() -> construct.call(*args)
                parameters.isNotEmpty() -> {
                    val values = parameters.map {
                        when (it.type.classifier!! as KClass<*>) {
                            Map::class -> HashMap<Any, Any?>().withDefault { null }
                            List::class -> ArrayList<Any?>()
                            else -> null
                        }
                    }
                    construct.call(*values.toTypedArray())
                }
                else -> construct.call()
            }
        }

        override fun <T : Any> newDataInstance(type: KClass<T>, parameterMap: Map<String, Any?>): T {
            val cons = type.primaryConstructor!!
            val args = cons.parameters.associateWith {
                parameterMap[it.name]
            }.filter { entry ->
                if(!entry.key.isOptional && entry.value == null){
                    if(entry.key.type.isMarkedNullable){
                        return@filter true
                    }else{
                        throw RuntimeException("${type.qualifiedName}的${entry.key.name}无法赋值为空")
                    }
                }
                entry.value != null
            }
            return cons.callBy(args)
        }

        override fun <T: Any> getFieldType(type: KClass<T>, members: List<String>): KClass<*>? {
            return getFieldType(type, 0, members)
        }


        private fun <T: Any> getFieldType(type: KClass<T>, start: Int, members: List<String>): KClass<*>? {
            var fieldType: KClass<*>?
            val fieldName = members[start]
            fieldType = type.declaredMemberProperties.find { it.name == fieldName }?.returnType?.classifier?.let { it as KClass<*> }
            if(fieldType == null){
                return null
            }
            return if(start + 1 < members.size){
                getFieldType(fieldType, start + 1, members)
            }else{
                fieldType
            }
        }

        override fun <T: Any> setFieldValue(obj: T, value: Any?, members: List<String>) {
            setFieldValue(obj, value, 0, members)
        }

        private fun <T: Any> setFieldValue(obj: T, value: Any?, start: Int, members: List<String>) {
            var field: Field
            val fieldName = members[start]
            try{
                field = obj.javaClass.getDeclaredField(fieldName)
                if(field == null){
                    return
                }
                if(!canAccess(obj, field)){
                    field.isAccessible = true
                }

                if(start == members.size - 1){
                    field.set(obj, value)
                }else{
                    val fieldValue = field.get(obj)?:let{
                        val fieldType = field.type.kotlin
                        if(fieldType.isData){
                            newDataInstance(fieldType, emptyMap()).also { fv -> field.set(obj, fv) }
                        }else{
                            newInstance(fieldType).also { fv -> field.set(obj, fv) }
                        }
                    }
                    setFieldValue(fieldValue, value, start + 1, members)
                }
            }catch (e: NoSuchFieldException){}
        }

        private fun <T: Any> canAccess(obj: T, field: Field): Boolean{
            return if(fieldCanAccess){
                field.canAccess(obj)
            }else{
                field.isAccessible
            }
        }

        override fun <T : Any> getFields(obj: T): List<Field> {
            return obj.javaClass.declaredFields.onEach {
                if(!canAccess(obj, it)){
                    it.isAccessible = true
                }
            }.toList()
        }

        override fun <T : Any> getFieldValue(field: Field, obj: T): Any? {
            if(!canAccess(obj, field)){
                field.isAccessible = true
            }
            return field.get(obj)
        }

        override fun <T : Any> getFieldValue(obj: T, vararg members: String): Any? {
            var value: Any? = obj
            for (member in members) {
                if(value == null)
                    return null
                value = getFieldValue(value.javaClass.getDeclaredField(member), value)
            }
            return value
        }

        override fun getGenericReturnTypeFromMethod(method: Method): Type? {
            return method.genericReturnType?.let{
                if(it is ParameterizedType){
                    it as ParameterizedType
                }else{
                    null
                }
            }?.let{
                it.actualTypeArguments[0]
            }
        }

        override fun toMap(obj: Any, filter: (Field) -> Boolean): LinkedHashMap<String, Any?> {
            return linkedMapOf(
                *(getFields(obj).filter(filter).map {
                    it.name to getFieldValue(it, obj)
                }.toTypedArray())
            )
        }

        override fun <T: Any> overrideProperties(fromObject: T, origin: T) {
            fromObject::class.memberProperties.filterIsInstance<KMutableProperty<*>>().map { fromProp ->
                val value = fromProp.getter.call(fromObject)
                fromProp.setter.call(origin, value)
            }
        }

        override fun getClassParameters(kClass: KClass<out Any>): List<KParameter> {
            return kClass.primaryConstructor?.parameters?: emptyList()
        }

    }

    /**
     * 无参构造函数实例化
     */
    fun <T: Any> newInstance(type: KClass<T>, vararg args: Any?): T

    /**
     * data class实例化
     */
    fun <T: Any> newDataInstance(type: KClass<T>, parameterMap: Map<String, Any?>): T

    /**
     * 按名称获取field类型
     */
    fun <T: Any> getFieldType(type: KClass<T>, members: List<String>): KClass<*>?

    /**
     * 设置field值
     */
    fun <T: Any> setFieldValue(obj: T, value: Any?, members: List<String>)

    /**
     * 获取所有field
     */
    fun <T: Any> getFields(obj: T): List<Field>

    /**
     * 获取field值
     */
    fun <T: Any> getFieldValue(field: Field, obj: T): Any?

    /**
     * 按名称路径获取field值
     */
    fun <T: Any> getFieldValue(obj: T, vararg members: String): Any?

    /**
     * 获取method的泛型返回类型
     */
    fun getGenericReturnTypeFromMethod(method: Method): Type?

    /**
     * 对象转Map
     */
    fun toMap(obj: Any, filter: (Field) -> Boolean = { true }): LinkedHashMap<String, Any?>

    /**
     * 复制属性到另外的对象
     */
    fun <T: Any> overrideProperties(fromObject: T, origin: T)

    /**
     * 获取主构造函数声明的属性
     */
    fun getClassParameters(kClass: KClass<out Any>): List<KParameter>

}